using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using ZoneFiveSoftware.Common.Data;
using ZoneFiveSoftware.Common.Data.Fitness;
using ZoneFiveSoftware.Common.Data.Measurement;
using ZoneFiveSoftware.Common.Visuals;
using FilteredStatistics.Controller;
using FilteredStatistics.Common;

namespace FilteredStatistics.Data
{
    class ActivityRangeInfoCache : IActivityRangeInfoCache
    {
        public ActivityRangeInfoCache(ValueRange<DateTime> range, IActivity activity)
        {
            m_Range = range;
            m_PauselessRange = new ValueRange<DateTime>(Utils.RemovePausedTimes(range.Lower, activity),
                                                        Utils.RemovePausedTimes(range.Upper, activity));
            m_Activity = activity;
            m_ActivityInfoCache = ActivityInfoCache.Instance.GetInfo(activity);
        }

        public bool ContainsStoredInfo(Guid infoId)
        {
            return m_AdditionalInfoCache.ContainsKey(infoId);
        }

        public void SaveStoredInfo(Guid infoId, object data)
        {
            if(ContainsStoredInfo(infoId))
            {
                m_AdditionalInfoCache[infoId] = data;
            }
            else
            {
                m_AdditionalInfoCache.Add(infoId, data);
            }
        }

        public object RetrieveStoredInfo(Guid infoId)
        {
            if(ContainsStoredInfo(infoId))
            {
                return m_AdditionalInfoCache[infoId];
            }

            return null;
        }

        private void BuildCachedDistanceTrack()
        {
            if (m_PauselessRangeDistanceTrack == null)
            {
                m_ActivityDistanceTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.MovingDistanceMetersTrack, m_Activity);
                m_PauselessRangeDistanceTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityDistanceTrack, PauselessRange, m_PauselessRangeDistanceTrack);
                m_RangeWithPausesDistanceTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.MovingDistanceMetersTrack, RangeWithPauses, m_RangeWithPausesDistanceTrack);
            }
        }

        private void BuildCachedCadenceTrack()
        {
            if (m_PauselessRangeCadenceTrack == null)
            {
                m_ActivityCadenceTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.SmoothedCadenceTrack, m_Activity);

                m_PauselessRangeCadenceTrack = new NumericTimeDataSeries();
                ExtractRangeFromDataTrack(m_ActivityCadenceTrack, PauselessRange, m_PauselessRangeCadenceTrack);

                m_RangeWithPausesCadenceTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.SmoothedCadenceTrack, RangeWithPauses, m_RangeWithPausesCadenceTrack);
            }
        }

        private void BuildCachedElevationTrack()
        {
            if (m_PauselessRangeElevationTrack == null)
            {
                m_ActivityElevationTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.SmoothedElevationTrack, m_Activity);

                m_PauselessRangeElevationTrack = new NumericTimeDataSeries();
                ExtractRangeFromDataTrack(m_ActivityElevationTrack, PauselessRange, m_PauselessRangeElevationTrack);

                m_RangeWithPausesElevationTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.SmoothedElevationTrack, RangeWithPauses, m_RangeWithPausesElevationTrack);
            }
        }

        private void BuildCachedGradeTrack()
        {
            if (m_PauselessRangeGradeTrack == null)
            {
                m_ActivityGradeTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.SmoothedGradeTrack, m_Activity);

                m_PauselessRangeGradeTrack = new NumericTimeDataSeries();
                ExtractRangeFromDataTrack(m_ActivityGradeTrack, PauselessRange, m_PauselessRangeGradeTrack);

                m_RangeWithPausesGradeTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.SmoothedGradeTrack, RangeWithPauses, m_RangeWithPausesGradeTrack);
            }
        }

        private void BuildCachedHeartRateTrack()
        {
            if (m_PauselessRangeHeartRateTrack == null)
            {
                m_ActivityHeartRateTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.SmoothedHeartRateTrack, m_Activity);

                m_PauselessRangeHeartRateTrack = new NumericTimeDataSeries();
                ExtractRangeFromDataTrack(m_ActivityHeartRateTrack, PauselessRange, m_PauselessRangeHeartRateTrack);

                m_RangeWithPausesHeartRateTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.SmoothedHeartRateTrack, RangeWithPauses, m_RangeWithPausesHeartRateTrack);
            }
        }

        private void BuildCachedSpeedTrack()
        {
            if (m_PauselessRangeSpeedTrack == null)
            {
                m_ActivitySpeedTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.SmoothedSpeedTrack, m_Activity);

                m_PauselessRangeSpeedTrack = new NumericTimeDataSeries();
                ExtractRangeFromDataTrack(m_ActivitySpeedTrack, PauselessRange, m_PauselessRangeSpeedTrack);

                m_RangeWithPausesSpeedTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.SmoothedSpeedTrack, RangeWithPauses, m_RangeWithPausesSpeedTrack);
            }
        }

        private void BuildCachedPowerTrack()
        {
            if (m_PauselessRangePowerTrack == null)
            {
                m_ActivityPowerTrack = Utils.RemovePausedTimesInTrack(m_ActivityInfoCache.SmoothedPowerTrack, m_Activity);

                m_PauselessRangePowerTrack = new NumericTimeDataSeries();
                ExtractRangeFromDataTrack(m_ActivityPowerTrack, PauselessRange, m_PauselessRangePowerTrack);

                m_RangeWithPausesPowerTrack = new DistanceDataTrack();
                ExtractRangeFromDataTrack(m_ActivityInfoCache.SmoothedPowerTrack, RangeWithPauses, m_RangeWithPausesPowerTrack);
            }
        }

        private void ExtractRangeFromDataTrack(INumericTimeDataSeries sourceTrack, ValueRange<DateTime> timeToKeep, INumericTimeDataSeries resultTrack)
        {
            resultTrack.Clear();

            if (sourceTrack != null && sourceTrack.Count > 0)
            {
                TimeSpan startElapsedTime = timeToKeep.Lower - sourceTrack.StartTime;
                TimeSpan endElapsedTime = timeToKeep.Upper - sourceTrack.StartTime;
                TimeSpan totalElapsedTime = timeToKeep.Upper - timeToKeep.Lower;
                int startElapsedSeconds = (int)startElapsedTime.TotalSeconds;
                int endElapsedSeconds = (int)endElapsedTime.TotalSeconds;
                int totalElapsedSeconds = (int)totalElapsedTime.TotalSeconds;
                DateTime timeStart = timeToKeep.Lower;
                DateTime timeEnd = timeToKeep.Upper;
                DateTime sourceDataEndTime = sourceTrack.StartTime + new TimeSpan(0, 0, (int)sourceTrack.TotalElapsedSeconds);

                // Readjust to match the track since it can start after the activity start time
                //  or end before the end time
                if (timeStart < sourceTrack.StartTime)
                {
                    timeStart = sourceTrack.StartTime;
                }
                else if (timeStart > sourceDataEndTime)
                {
                    timeStart = sourceDataEndTime;
                }

                if (timeEnd < sourceTrack.StartTime)
                {
                    timeEnd = sourceTrack.StartTime;
                }
                else if (timeEnd > sourceDataEndTime)
                {
                    timeEnd = sourceDataEndTime;
                }

                ITimeValueEntry<float> startValue = sourceTrack.GetInterpolatedValue(timeStart);
                int currentItemIndex = sourceTrack.IndexOf(startValue);

                // Since we use an interpolated value, it is possible that there is no matching
                //  index, in which case we need to find our start index
                if (currentItemIndex == -1)
                {
                    currentItemIndex = 0;

                    foreach (ITimeValueEntry<float> entry in sourceTrack)
                    {
                        if (entry.ElapsedSeconds >= startElapsedSeconds)
                        {
                            break;
                        }

                        ++currentItemIndex;
                    }
                }
                else
                {
                    // Start at the next index since we always add the start
                    ++currentItemIndex;
                }

                // Add the first point to the result
                resultTrack.Add(timeStart.ToLocalTime(), startValue.Value);

                // Just make sure we have a start index.  Can be == -1 if there is no track?
                if (currentItemIndex != -1)
                {
                    // Now go through all indexes until we hit the end
                    while (currentItemIndex < sourceTrack.Count && sourceTrack[currentItemIndex].ElapsedSeconds <= endElapsedSeconds)
                    {
                        ITimeValueEntry<float> currentDistance = sourceTrack[currentItemIndex];
                        DateTime currentTime = timeStart + new TimeSpan(0, 0, (int)(currentDistance.ElapsedSeconds - startValue.ElapsedSeconds));

                        resultTrack.Add(currentTime.ToLocalTime(), currentDistance.Value);

                        ++currentItemIndex;
                    }
                }

                // Since we check the elapsed seconds, it is possible that our end point needs to
                //  be interpolated.  Let's make sure we have our end point in our cached data
                ITimeValueEntry<float> lastEntry = sourceTrack.GetInterpolatedValue(timeEnd);

                // Should never be empty, we at least added the start above...
                if (resultTrack.Count != 0 && lastEntry != null)
                {
                    if (resultTrack[resultTrack.Count - 1].ElapsedSeconds != totalElapsedSeconds)
                    {
                        // Add our final interpolated point
                        resultTrack.Add(timeEnd.ToLocalTime(), lastEntry.Value);
                    }
                }
            }
        }

        public ValueRange<DateTime> PauselessRange
        {
            get { return m_PauselessRange; }
        }

        public ValueRange<DateTime> RangeWithPauses
        {
            get { return m_Range; }
        }

        public IActivity Activity
        {
            get { return m_Activity; }
        }

        public ActivityInfo ActivityInfo
        {
            get { return m_ActivityInfoCache; }
        }

        public INumericTimeDataSeries ActivityPauselessCadenceTrack
        {
            get
            {
                BuildCachedCadenceTrack();

                return m_ActivityCadenceTrack;
            }
        }

        public INumericTimeDataSeries ActivityPauselessDistanceTrack
        {
            get
            {
                BuildCachedDistanceTrack();

                return m_ActivityDistanceTrack;
            }
        }

        public INumericTimeDataSeries ActivityPauselessElevationTrack
        {
            get
            {
                BuildCachedElevationTrack();

                return m_ActivityElevationTrack;
            }
        }

        public INumericTimeDataSeries ActivityPauselessGradeTrack
        {
            get
            {
                BuildCachedGradeTrack();

                return m_ActivityGradeTrack;
            }
        }

        public INumericTimeDataSeries ActivityPauselessHeartRateTrack
        {
            get
            {
                BuildCachedHeartRateTrack();

                return m_ActivityHeartRateTrack;
            }
        }

        public INumericTimeDataSeries ActivityPauselessPowerTrack
        {
            get
            {
                BuildCachedPowerTrack();

                return m_ActivityPowerTrack;
            }
        }

        public INumericTimeDataSeries ActivityPauselessSpeedTrack
        {
            get
            {
                BuildCachedSpeedTrack();

                return m_ActivitySpeedTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesCadenceTrack
        {
            get
            {
                BuildCachedCadenceTrack();

                return m_RangeWithPausesCadenceTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesDistanceTrack
        {
            get
            {
                BuildCachedDistanceTrack();

                return m_RangeWithPausesDistanceTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesElevationTrack
        {
            get
            {
                BuildCachedElevationTrack();

                return m_RangeWithPausesElevationTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesGradeTrack
        {
            get
            {
                BuildCachedGradeTrack();

                return m_RangeWithPausesGradeTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesHeartRateTrack
        {
            get
            {
                BuildCachedHeartRateTrack();

                return m_RangeWithPausesHeartRateTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesPowerTrack
        {
            get
            {
                BuildCachedPowerTrack();

                return m_RangeWithPausesPowerTrack;
            }
        }

        public INumericTimeDataSeries RangeWithPausesSpeedTrack
        {
            get
            {
                BuildCachedSpeedTrack();

                return m_RangeWithPausesSpeedTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangeCadenceTrack
        {
            get
            {
                BuildCachedCadenceTrack();

                return m_PauselessRangeCadenceTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangeDistanceTrack
        {
            get
            {
                BuildCachedDistanceTrack();

                return m_PauselessRangeDistanceTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangeElevationTrack
        {
            get
            {
                BuildCachedElevationTrack();

                return m_PauselessRangeElevationTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangeGradeTrack
        {
            get
            {
                BuildCachedGradeTrack();

                return m_PauselessRangeGradeTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangeHeartRateTrack
        {
            get
            {
                BuildCachedHeartRateTrack();

                return m_PauselessRangeHeartRateTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangePowerTrack
        {
            get
            {
                BuildCachedPowerTrack();

                return m_PauselessRangePowerTrack;
            }
        }

        public INumericTimeDataSeries PauselessRangeSpeedTrack
        {
            get
            {
                BuildCachedSpeedTrack();

                return m_PauselessRangeSpeedTrack;
            }
        }

        ValueRange<DateTime> m_Range;
        ValueRange<DateTime> m_PauselessRange;
        IActivity m_Activity;
        ActivityInfo m_ActivityInfoCache = null;

        INumericTimeDataSeries m_ActivityCadenceTrack;
        INumericTimeDataSeries m_ActivityDistanceTrack;
        INumericTimeDataSeries m_ActivityElevationTrack;
        INumericTimeDataSeries m_ActivityGradeTrack;
        INumericTimeDataSeries m_ActivityHeartRateTrack;
        INumericTimeDataSeries m_ActivityPowerTrack;
        INumericTimeDataSeries m_ActivitySpeedTrack;

        INumericTimeDataSeries m_PauselessRangeCadenceTrack = null;
        INumericTimeDataSeries m_PauselessRangeDistanceTrack = null;
        INumericTimeDataSeries m_PauselessRangeElevationTrack = null;
        INumericTimeDataSeries m_PauselessRangeGradeTrack = null;
        INumericTimeDataSeries m_PauselessRangeHeartRateTrack = null;
        INumericTimeDataSeries m_PauselessRangePowerTrack = null;
        INumericTimeDataSeries m_PauselessRangeSpeedTrack = null;

        INumericTimeDataSeries m_RangeWithPausesCadenceTrack = null;
        INumericTimeDataSeries m_RangeWithPausesDistanceTrack = null;
        INumericTimeDataSeries m_RangeWithPausesElevationTrack = null;
        INumericTimeDataSeries m_RangeWithPausesGradeTrack = null;
        INumericTimeDataSeries m_RangeWithPausesHeartRateTrack = null;
        INumericTimeDataSeries m_RangeWithPausesPowerTrack = null;
        INumericTimeDataSeries m_RangeWithPausesSpeedTrack = null;

        Dictionary<Guid, object> m_AdditionalInfoCache = new Dictionary<Guid, object>();
    }
}
